#include <g-bios.h>
#include <sysconf.h>
#include <core/fs.h>
#include <core/getopt.h>
#include <net/tftp.h>
#include <flash/part.h>
#include <arch/arm.h> // fixme: to be removed
#include "atag.h"

#define CONF_FS_PART_ONLY // fixme: move to autoconf.h

#define LINUX_IMAGE_NAME "zImage"


typedef void (*LINUX_KERNEL_ENTRY)(int, int, ULONG);


static struct Atag *BeginSetupTag (void *pTabBase)
{
	struct Atag *pCurTag;

	pCurTag = (struct Atag *)pTabBase;

	pCurTag->stHdr.ulTag = ATAG_CORE;
	pCurTag->stHdr.nSize = TAG_SIZE(TagCore);

	pCurTag->stCore.ulFlags   = 0;
	pCurTag->stCore.nPageSize = 0;
	pCurTag->stCore.ulRootDev = 0;
	
	return pCurTag;
}

static struct Atag *SetupCmdlineTag(struct Atag *pCurTag, char *pchCmdline)
{
	pCurTag = TAG_NEXT(pCurTag);

	while (*pchCmdline && ' ' == *pchCmdline)
		pchCmdline++;

	pCurTag->stHdr.ulTag = ATAG_CMDLINE;
	pCurTag->stHdr.nSize = (sizeof(struct TagHeader) + strlen(pchCmdline) + 3) >> 2;

	strcpy(pCurTag->stCmdLine.cCmdLine, pchCmdline);

	return pCurTag;
}


// fixme
static struct Atag *SetupMemTag (struct Atag *pCurTag)
{
	int i;

	for (i = 0; i < CONF_RAM_BANK_NUM; i++) 
	{
		pCurTag = TAG_NEXT(pCurTag);

		pCurTag->stHdr.ulTag = ATAG_MEM;
		pCurTag->stHdr.nSize = TAG_SIZE(TagMem32);

		pCurTag->stMem.ulStart = SDRAM_BASE;
		pCurTag->stMem.nSize   = SDRAM_SIZE;
	}

	return pCurTag;
} 


static struct Atag *SetupRamdiskTag(struct Atag *pCurTag, struct ImageCache *pImgRamdisk)
{
	pCurTag = TAG_NEXT(pCurTag);

	pCurTag->stHdr.ulTag = ATAG_RAMDISK;
	pCurTag->stHdr.nSize = TAG_SIZE(TagRamDisk);
	pCurTag->stRamDisk.dwFlags = 3; // fixme!!
	pCurTag->stRamDisk.nStart = (UINT32)pImgRamdisk->pCacheBase;
	pCurTag->stRamDisk.nSize  = pImgRamdisk->ulCacheLen;

	return pCurTag;
}


static struct Atag *SetupInitrdTag(struct Atag *pCurTag, struct ImageCache *pImgRamdisk)
{
	pCurTag = TAG_NEXT(pCurTag);

	pCurTag->stHdr.ulTag = ATAG_INITRD2;
	pCurTag->stHdr.nSize = TAG_SIZE(TagInitrd);
	pCurTag->stInitRd.ulStart = (UINT32)pImgRamdisk->pCacheBase;
	pCurTag->stInitRd.nSize  = pImgRamdisk->ulCacheLen;

	return pCurTag;
}


static void EndSetupTag (struct Atag *pCurTag)
{
	pCurTag = TAG_NEXT(pCurTag);

	pCurTag->stHdr.ulTag = ATAG_NONE;
	pCurTag->stHdr.nSize = 0;
}

// fixme
static int BootUsage(void)
{
	printf("Usage: boot [options] \n" 
		"\noptions:\n"
		"\t-t [kernel_image_name]:  download kernel image via tftp.\n"
		"\t-f [<part_num>]:  root=/dev/mtdblock<part_num>\n"
		"\t-n [nfs_server:/path/to/nfs/root]:  boot vis NFS\n"
		"\t-r [ramdisk_image_name]:  boot with Ramdisk\n"
		"\t-v:  print the linux parameters and quit\n"
		"\nExamples:\n"
		"\t..."
		);

	return 0;
}


static int PartLoadImage(PART_TYPE nType, struct ImageCache **ppImgCache)
{
	char szImgName[MAX_FILE_NAME_LEN];
	ULONG ulImgSize;
	int nPartNum;
	int nErrCode = 0;
	char szErrMsg[64];
	int nIndex;
	ULONG ulOffset;
	struct PartAttr vAttr[MAX_FLASH_PARTS];
	struct Flash *pFlash;
	struct Partition *pPart;
	struct ImageCache *pImgCache;


	printf("Loading %s image from flash ... ",
		nType == PT_OS_LINUX ? "kernel" : "ramdisk"
		);

	pImgCache = GuImageCacheGet(nType);
	BUG_IF (NULL == pImgCache || NULL == pImgCache->pCacheBase);

	pFlash = GuFlashOpen(BOOT_FLASH_ID);
	if (!pFlash)
	{
		strcpy(szErrMsg, "cannot open flash");
		nErrCode = -ENODEV;
		goto L0;
	}

	nPartNum = GuPartTabRead(pFlash, vAttr, MAX_FLASH_PARTS);
	if (nPartNum < 0)
	{
		strcpy(szErrMsg, "cannot read partition table");
		goto L1;
	}

	for (nIndex = 0; nIndex < nPartNum && vAttr[nIndex].nPartType != nType; ++nIndex);

	if (nIndex == nPartNum)
	{
		strcpy(szErrMsg, "cannot find the partition");
		nErrCode = -1;
		goto L1;
	}

	pPart = GuPartOpen(nIndex);
	if (NULL == pPart)
	{
		strcpy(szErrMsg, "cannot open partition");
		nErrCode = -1;
		goto L1;
	}

	nErrCode = GuPartGetImgInfo(pPart, szImgName, &ulImgSize);
	if (nErrCode < 0 || ulImgSize == 0)
	{
		strcpy(szErrMsg, "no image, pls download it first");
		nErrCode = -1;
		goto L2;
	}

	ulOffset = GuPartGetBase(pPart);

	nErrCode = GuFlashRead(pFlash, pImgCache->pCacheBase, (int *)&ulOffset, ulImgSize);
	if (nErrCode < 0)
	{
		strcpy(szErrMsg, "flash read failed");
		goto L2;
	}

	pImgCache->ulCacheLen = ulImgSize;

	*ppImgCache = pImgCache;

L2:
	GuPartClose(pPart);
L1:
	GuFlashClose(pFlash);
L0:
	if (nErrCode >= 0)
	{
		puts("OK.");
	}
	else
	{
		printf("failed! (%s)\n", szErrMsg);
	}

	return nErrCode;
}


static int TftpLoadImage(PART_TYPE nType, char szImgName[], struct ImageCache **ppImgCache)
{
	int ret;
	struct TftpOpt dlopt;
	struct ImageCache *pImgCache;


	printf("Downloading %s image via tftp:\n",
		nType == PT_OS_LINUX ? "linux" : "ramdisk"
		);

	pImgCache = GuImageCacheGet(nType);
	BUG_IF (NULL == pImgCache || NULL == pImgCache->pCacheBase);

	memset(&dlopt, 0x0, sizeof(dlopt));
	
	GuNetGetTftpSvr(&dlopt.dwSvrIP);
	strcpy(dlopt.szFileName, szImgName);
	dlopt.pLoadAddr = pImgCache->pCacheBase;

	ret = GuNetTftpGetFile(&dlopt);
	if (ret < 0)
	{
		printf("fail to download %s!\n", szImgName);
		goto L1;
	}

	pImgCache->ulCacheLen = ret;

	*ppImgCache = pImgCache;

L1:
	return ret;
}


// fixme: the awful code
static int BuildCmdline(char *pchCmdline)
{
	int  ret = 0;
	char *str = pchCmdline;
	struct SysConf    *pSysConf;
	struct PartInfo   *pMbrConf;
	struct NetConf    *pNetConf;
	struct LinuxParam *pKeParam;
	struct Flash      *pFlash;
	struct Partition  *pPart, *pRootPart;
	char szLocal[IPV4_STR_LEN], szNetMask[IPV4_STR_LEN];
	char szMtdDev[MTD_DEV_NAME_LEN];
	char szPartList[512];
	UINT32 nPartNum;
	int nMtdRoot = 0, nRootIdx;
	UINT32 nPartIdx;


	ret = GuSysConfLoad(&pSysConf);
	if (ret < 0)
	{
		goto L1;
	}

	pMbrConf = &pSysConf->mPartInfo;
	pNetConf = &pSysConf->mNetConf;
	pKeParam = &pSysConf->mLinuxParam;


	GuIpToStr(szLocal, pNetConf->dwLocalIP);
	GuIpToStr(szNetMask, pNetConf->dwNetMask);

#if 1
	// fixme: netmask, ..
	str += sprintf(str, "ip=%s:%s:%s:%s:maxwit.googlecode.com:eth0:off",
				szLocal, pKeParam->szNfsSvr, pKeParam->szNfsSvr, szNetMask);
#endif

	str += sprintf(str, " console=%s,115200", pKeParam->szConDev);

	pFlash = GuFlashOpen(BOOT_FLASH_ID);
	if (NULL == pFlash)
	{
		ret = -ENODEV;
		goto L1;
	}

	GuFlashGetMtdName(pFlash, szMtdDev);

	nPartNum = GuPartTabRead(pFlash, NULL, 0);

	GuFlashClose(pFlash);


	nRootIdx = GuSysConfGetRoot();

	if (szMtdDev[0] && nPartNum > 0 && nPartNum <= MAX_FLASH_PARTS)
	{
		char *pPartStr = szPartList;

		pPartStr += sprintf(pPartStr, "mtdparts=%s:", szMtdDev);

		for (nPartIdx = 0; nPartIdx < nPartNum; nPartIdx++)
		{
			const char *pName;
			PART_TYPE	enType;

			pPart = GuPartOpen(nPartIdx);
			BUG_IF (NULL == pPart);
			
			switch (enType = GuPartGetType(pPart))
			{
			default:
#ifdef CONF_FS_PART_ONLY
				break;
#endif

			case PT_FS_JFFS2:
			case PT_FS_YAFFS:
			case PT_FS_YAFFS2:
			case PT_FS_CRAMFS:
				pPartStr += sprintf(pPartStr, "0x%x@0x%x(%s),", 
								GuPartGetSize(pPart),
								GuPartGetBase(pPart),
								GuPartGetName(pPart->pAttr)
								);

				if (nPartIdx < nRootIdx)
					nMtdRoot++;

				break;
			}

			GuPartClose(pPart);
		}

		*--pPartStr = '\0';
	}


	if (pKeParam->dwBootMode & BM_NFS)
	{
		str += sprintf(str, " root=/dev/nfs rw nfsroot=%s:%s", 
					pKeParam->szNfsSvr, pKeParam->szNfsPath);
	}
	else if (pKeParam->dwBootMode & BM_FLASHDISK)
	{
		int nRootType;

		pRootPart = GuPartOpen(nRootIdx);
		BUG_IF (NULL == pRootPart);

		nRootType = GuPartGetType(pRootPart);

		GuPartClose(pRootPart);

		switch (nRootType)
		{
		case PT_FS_CRAMFS:
		case PT_FS_JFFS2:
		case PT_FS_YAFFS:
		case PT_FS_YAFFS2:
			str += sprintf(str, " root=/dev/mtdblock%d rw rootfstype=%s", // fixme
						nMtdRoot, GuPartTypeToStr(nRootType)
						);
			break;

		default:
			printf("partition %d is NOT set for filesystem!\n", nRootIdx);
			goto L1;
		}
	}

	str += sprintf(str, " %s", szPartList);

	return 0;

L1:
	printf("Fail to setup linux command line!\n");

	return ret;
}

static int ShowBootArgs(void *pTagBase)
{
	int i;
	struct Atag *pTag = pTagBase;


	if (pTag->stHdr.ulTag != ATAG_CORE)
	{
		printf("Invalid ATAG pointer (0x%08x)!\n", pTag);
		return -EINVAL;
	}

	i = 0;
	printf("\n");

	while (1)
	{
		printf("[ATAG %d] ", ++i);

		switch (pTag->stHdr.ulTag)
		{
		case ATAG_CORE:
			printf("****** ATAG Begin ******\n");
			break;

		case ATAG_CMDLINE:
			printf("Kernel command line:\n%s\n", pTag->stCmdLine.cCmdLine);
			// printf("Kernel command line\n");
			break;

		case ATAG_MEM:
			printf("Memory: [0x%08x - 0x%08x]\n",
				pTag->stMem.ulStart, pTag->stMem.ulStart + pTag->stMem.nSize
				);
			break;

		case ATAG_INITRD2:
			printf("Initrd: [0x%08x - 0x%08x]\n",
				pTag->stInitRd.ulStart, pTag->stInitRd.ulStart + pTag->stInitRd.nSize
				);
			break;

		case ATAG_NONE:
			printf("******  ATAG End  ******\n");
			return 0;

		default: // fixme
			printf("tag = 0x%08x\n", pTag->stHdr.ulTag);
			break;
		}

		pTag = TAG_NEXT(pTag);
	}

	return 0;
}

static int main(int argc, char *argv[])
{
	int   i, ret;
	ULONG ulDevNum;
	char szCmdline[DEFAULT_KCMDLINE_LEN];
	BOOL isShowArg = FALSE;
	struct TftpOpt dlopt;
	struct ImageCache *pImgKernel, *pImgRamdisk;
	struct SysConf  *  pSysConf;
	struct PartInfo   *pPartInfo;
 	struct NetConf    *pNetConf;
	struct LinuxParam *pKeParam;
	struct Atag *pTag;
	LINUX_KERNEL_ENTRY pfExecKernel;


	GuSysGetPartInfo(&pPartInfo);
	GuSysGetNetConf(&pNetConf);
	GuSysGetLinuxParam(&pKeParam);

	// fixme
	for (i = 1; i < argc; i++)
	{
		char *pArg = argv[i];
		UINT32 dwNfsIp;


		if ('-' != pArg[0] || strlen(pArg) != 2)
		{
			BootUsage();
			return -EINVAL;
		}

		switch (pArg[1])
		{
		case 't':
			if (i + 1 == argc || '-' == argv[i + 1][0])
			{
				pKeParam->szImgKernel[0] = '\0';
				break;
			}

			strcpy(pKeParam->szImgKernel, argv[++i]);

			break;

		case 'r':
			pKeParam->dwBootMode = BM_RAMDISK;

			if (i + 1 == argc || '-' == argv[i + 1][0])
			{
				pKeParam->szImgRd[0] = '\0';
				break;
			}

			strcpy(pKeParam->szImgRd, argv[++i]);

			break;

		case 'f':
			pKeParam->dwBootMode = BM_FLASHDISK;

			if (i + 1 == argc || '-' == argv[i + 1][0])
				break;

			if (GuStrToVal(argv[++i], &ulDevNum) < 0)
			{
				printf("Invalid partition number (%s)!\n", argv[i]);
				break;
			}

			// fixme: if num > nParts
			pKeParam->nRootDev = ulDevNum;

			break;

		case 'n':
			pKeParam->dwBootMode = BM_NFS;

			if (i + 1 == argc || '-' == argv[i + 1][0])
				break;

			pArg = argv[++i];

			while (*pArg && *pArg != ':' && *pArg != '/') pArg++;

			switch (*pArg)
			{
			case ':':
				*pArg = '\0';
				strcpy(pKeParam->szNfsPath, pArg + 1);				

			case '\0':
				if (GuStrToIp((BYTE *)&dwNfsIp, argv[i]) >= 0)
					strcpy(pKeParam->szNfsSvr, argv[i]);
				else
					printf("wrong ip format! (%s)\n", argv[i]);

				break;

			default:
				strcpy(pKeParam->szNfsPath, argv[i]);
				break;
			}

			break;

		case 'c':
			if (i + 1 == argc || '-' == argv[i + 1][0])
			{
				printf("Invalid option: %s", argv[i]);
				BootUsage();
				return -EINVAL;
			}

			strcpy(pKeParam->szConDev, argv[++i]);

			break;

		case 'v':
			isShowArg = TRUE;
			break;

		case 'h':
			BootUsage();
			return 0;

		default:
			printf("Invalid option: \"%s\"!\n", pArg);
			BootUsage();
			return -EINVAL;
		}
	}


	if (argc > 2 || (2 == argc && FALSE == isShowArg))
	{
		GuSysConfStore();
	}

	BuildCmdline(szCmdline);

	BUG_IF ((pKeParam->dwBootMode & ~BM_MASK) == 0);

	if (pKeParam->szImgKernel[0] != '\0') // fixme for error return (i.e. permission denied)
	{
		ret = TftpLoadImage(PT_OS_LINUX, pKeParam->szImgKernel, &pImgKernel);
	}
	else
	{
		ret = PartLoadImage(PT_OS_LINUX, &pImgKernel);
	}

	// TODO: check the kernel image

	if (ret < 0)
	{
		goto L1;
	}

	pfExecKernel = (LINUX_KERNEL_ENTRY)pImgKernel->pCacheBase;

	printf("\n");


	pTag = BeginSetupTag((void *)ATAG_BASE);

	pTag = SetupCmdlineTag(pTag, szCmdline);

	if (pKeParam->dwBootMode & BM_RAMDISK)
	{
		if (pKeParam->szImgRd[0] != '\0')
		{
			ret = TftpLoadImage(PT_FS_RAMDISK, pKeParam->szImgRd, &pImgRamdisk);
		}
		else
		{
			ret = PartLoadImage(PT_FS_RAMDISK, &pImgRamdisk);
		}

		if (ret < 0)
		{
			goto L1;
		}

		// TODO: check the ramdisk

		pTag = SetupInitrdTag(pTag, pImgRamdisk);
	}

	pTag = SetupMemTag(pTag);

	EndSetupTag(pTag);


	if (isShowArg)
	{
		ShowBootArgs((void *)ATAG_BASE);
		return 0;
	}

	IrqDisable(); // fixme

	pfExecKernel(0, MACH_ID, ATAG_BASE);

L1:
	printf("Fail to boot linux!\n");

	return ret;
}

INSTALL_APPLICATION(boot, main, "boot Linux kernel.");


void __INIT__ AutoBootGBios(void)
{
	int iTimeOut = 3;
	char *argv[1] = {"boot"};

	while (iTimeOut) 
	{
		int index;

		printf("\rAutoboot after %d seconds. Press any key to interrupt.", iTimeOut);

		for (index = 0; index < 10; index++)
		{
			mdelay(100);

			// non-block read
			if (GkGetUartStatus())
			{
				raw_getchar();
				putchar('\n');
				return;
			}
		}

		iTimeOut--;
	}

	putchar('\n');

	main(1, argv); // fixme
}


